package com.otp.webapp.action;

import java.io.IOException;
import java.io.OutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.Random;

import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.appfuse.model.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

import com.otp.model.test.instance.TestInstance;
import com.otp.model.test.instance.TestInstanceScore;
import com.otp.model.test.instance.TestInstanceState;
import com.otp.model.test.instance.TestItemResponseInstance;
import com.otp.model.test.template.Attribute;
import com.otp.model.test.template.AttributeSet;
import com.otp.model.test.template.Item;
import com.otp.model.test.template.ResponseScoreOption;
import com.otp.model.test.template.TestSection;
import com.otp.model.test.template.TestTemplate;
import com.otp.service.instance.ItemManager;
import com.otp.service.instance.ResponseScoreOptionManager;
import com.otp.service.instance.TestInstanceManager;
import com.otp.service.instance.TestItemResponseInstanceManager;
import com.otp.service.instance.TestSectionManager;
import com.otp.service.instance.User1Manager;
import com.otp.service.util.MathematicalFunctions;
import com.otp.webapp.action.util.PDFReportManager;
import com.otp.webapp.action.util.TestScoreUtil;
import com.otp.webapp.comparators.CompAnsItemsByItemOrderInSection;
import com.otp.webapp.comparators.CompSectionsByOrder;

/**
 * JSF Page class to handle editing a user with a form.
 * 
 */
@Scope("session")
@Component("userTest")
public class UserTest extends BasePage implements Serializable {
	private static final long serialVersionUID = -1141119853856863204L;

	String algorithm;

	private double maxTime;
	private int maxItems;
	private double minError;

	private long testStartTime;
	private long currentTime;
	private double testTime;
	private double error;
	private int itemCount;
	private int pageCount;

	private TestInstance testInstance;
	private TestTemplate testTemplate;
	private List<TestSection> sections;
	private List<TestTemplate> subtests;

	private ItemManager itemManager;
	private TestInstanceManager testInstanceManager;
	private TestSectionManager testSectionManager;
	private TestItemResponseInstanceManager itemResponseManager;
	private ResponseScoreOptionManager responseScoreOptionManager;
	private User1Manager user1Manager;

	private List<List<Item>> allSimpleItems;
	private List<List<AnsweredItem>> allAnswItems;
	private List<AnsweredItem> currentAnswerItems;
	private List<DisplayTestResult> displayTestResults;
	private List<AdaptiveMethods> adaptivParamList;

	int currentSectionPos = 0;
	List<Integer> inIndices = new ArrayList<Integer>();
	List<Integer> outIndices = new ArrayList<Integer>();

	private Random rand;
	private boolean displayDemo;

	private Long tic, tic2;
	private Long tac, tac2;

	private boolean timeMeasurements = false;
	private boolean saveResults = false;
	private boolean randomSeed = true;

	// FLAGS and getters only
	// ----------------------------------------------------------------------------------------------------
	// -----------------------------------------------------------------------------------------------------
	// -----------------------------------------------------------------------------------------------------

	public boolean isPrevious() {// Implement prev button
		if (isBySection() && currentSectionPos != 0)
			return true;
		return false;
	}

	public boolean isNext() {
		if (isBySection() && currentSectionPos == getTotalSections() - 1)
			return false;
		return true;
	}

	public boolean isSubmitTest() {
		return !isNext();
	}

	public boolean isTable() {
		return false; // TODO make dynamic ?????
	}

	public boolean isSingleItem() {
		if (algorithm == null || algorithm.length() == 0) {
			System.out
			.println("Trying to set single item flag, while no algorithm is defined");
			return false;
		}
		if (algorithm.equals("RANDOM")
				|| algorithm.equals("ADAPTIVE_DICHOTOMOUS")
				|| algorithm.equals("ADAPTIVE_POLYTOMOUS"))
			return true;
		else
			return false;
	}

	public boolean isBySection() {
		if (algorithm == null || algorithm.length() == 0) {
			System.out
			.println("Trying to get  bySection flag for undefined algorithm");
			return false;
		}
		if (algorithm.equals("STANDARD_NON_ADAPTIVE")
				|| algorithm.equals("STATIC_DICHOTOMOUS")
				|| algorithm.equals("STATIC_POLYTOMOUS"))
			return true;
		else
			return false;
	}

	public boolean isStatic() {
		if (algorithm == null || algorithm.length() == 0) {
			System.out
			.println("Trying to get isStatic() for undefined algorithm");
			return true;
		}
		if (algorithm.equals("STANDARD_NON_ADAPTIVE")
				||algorithm.equals("STATIC_DICHOTOMOUS")
				||algorithm.equals("STATIC_POLYTOMOUS"))
			return true;
		else
			return false;
	}

	public boolean isDichot() {
		if (algorithm == null || algorithm.length() == 0) {
			System.out
			.println("Trying to get isDichot() for undefined algorithm");
			return true;
		}
		if (algorithm.equals("ADAPTIVE_DICHOTOMOUS")
				|| algorithm.equals("ADAPTIVE_DICHOTOMOUS_MULT_ITEMS")
				|| algorithm.equals("STATIC_DICHOTOMOUS"))
			return true;
		return false;
	}

	public boolean isPolyt() {
		if (algorithm == null || algorithm.length() == 0) {
			System.out
			.println("Trying to get isPolyt() for undefined algorithm");
			return true;
		}
		if (algorithm.equals("ADAPTIVE_POLYTOMOUS")
				|| algorithm.equals("ADAPTIVE_POLYTOMOUS_MULT_ITEMS")
				|| algorithm.equals("STATIC_POLYTOMOUS"))
			return true;
		return false;
	}

	public boolean isApplyStopCondition() {
		if (isStatic())
			return false;
		else
			return true;
	}

	public boolean isItemStop() {
		if (isApplyStopCondition() && maxItems > 0)
			if (itemCount > maxItems - 1)
				return true;
		return false;
	}

	public boolean isItemPerSubtestStop() {
		if (isApplyStopCondition() && maxItems > 0)
			if (pageCount > maxItems - 1) {
				System.out.println("item stop!!!");
				return true;
			}
		return false;
	}

	public boolean isTimeStop() {
		if (isApplyStopCondition() && maxTime > 0)
			if (testTime > maxTime) {
				System.out.println("time stop!!!");
				return true;
			}
		return false;
	}

	public boolean isIRTScored() {
		if (algorithm == null || algorithm.length() == 0) {
			System.out
			.println("Trying to get isAdaptive() for undefined algorithm");
			return true;
		}
		if (algorithm.equals("STANDARD_NON_ADAPTIVE")
				|| algorithm.equals("RANDOM"))
			return false;
		return true;
	}
	
	public boolean isAdaptive() {
		if (algorithm == null || algorithm.length() == 0) {
			System.out
			.println("Trying to get isAdaptive() for undefined algorithm");
			return true;
		}
		if (algorithm.equals("STANDARD_NON_ADAPTIVE")
				|| algorithm.equals("RANDOM")
				|| algorithm.equals("STATIC_DICHOTOMOUS")
				|| algorithm.equals("STATIC_POLYTOMOUS"))
			return false;
		return true;
	}

	public boolean isErrorStop() {
		if (isApplyStopCondition() && isIRTScored() && (minError > 0))
			if (error < minError) {
				System.out.println("error stop!!!");
				return true;
			}
		return false;
	}

	public boolean isDisplayResult() {
		if (testInstance == null) {
			System.out
			.println("Error trying to set display flag, testInstance is null");
			return false;
		}
		if (testInstance.getTestInstanceState().getState().equals("COMPLETED")
				&& testInstance.isShowResult())
			return true;
		return false;
	}

	public boolean isDisplayDemo() {
		return displayDemo;
	}

	public boolean isDisplayInstructions() {
		if (testTemplate.getInstructions() == null
				|| testTemplate.getInstructions().isEmpty())
			return false;
		if (isBySection())
			if (currentSectionPos == 0)
				return true;
		if (isSingleItem())
			if (itemCount == 1)
				return true;
		if (pageCount == 1)
			return true;
		return false;
	}

	public void setDisplayDemo(boolean displayDemo) {
		this.displayDemo = displayDemo;
	}

	public String getCurrentSectionDescription() {
		if (isBySection())
			return "Section " + (currentSectionPos + 1) + " out of "
			+ getTotalSections();
		if (isSingleItem())
			return "Item " + itemCount;
		return "Page " + pageCount;
	}

	public Integer getTotalSections() {
		if (sections == null)
			return 0;
		else
			return sections.size();
	}

	public TestTemplate getTestTemplate() {
		return testTemplate;
	}

	// Setter and Getter Pairs
	// ----------------------------------------------------------------------------------------------------
	// -----------------------------------------------------------------------------------------------------
	// -----------------------------------------------------------------------------------------------------

	public List<DisplayTestResult> getDisplayTestResults() {
		return displayTestResults;
	}

	public void setDisplayTestResults(List<DisplayTestResult> displayTestResult) {
		this.displayTestResults = displayTestResult;
	}

	public List<AnsweredItem> getCurrentAnswerItems() {
		if (currentAnswerItems == null)
			currentAnswerItems = new ArrayList<AnsweredItem>();
		return currentAnswerItems;
	}

	public void setCurrentAnswerItems(List<AnsweredItem> currentAnswerItems) {
		this.currentAnswerItems = currentAnswerItems;
	}

	public TestInstance getTestInstance() {
		return testInstance;
	}

	public void setTestInstance(TestInstance testInstance) {
		this.testInstance = testInstance;
	}

	// Managers here
	// ----------------------------------------------------------------------------------------------------
	// -----------------------------------------------------------------------------------------------------
	// -----------------------------------------------------------------------------------------------------

	@Autowired
	public void setItemManager(@Qualifier("itemManager") ItemManager itemManager) {
		this.itemManager = itemManager;
	}

	@Autowired
	public void setTestInstanceManager(
			@Qualifier("testInstanceManager") TestInstanceManager testInstanceManager) {
		this.testInstanceManager = testInstanceManager;
	}

	@Autowired
	public void setTestSectionManager(
			@Qualifier("testSectionManager") TestSectionManager testSectionManager) {
		this.testSectionManager = testSectionManager;
	}

	@Autowired
	public void setItemResponseManager(
			@Qualifier("itemResponseManager") TestItemResponseInstanceManager itemResponseManager) {
		this.itemResponseManager = itemResponseManager;
	}

	@Autowired
	public void setResponseScoreOptionManager(
			@Qualifier("responseScoreOptionManager") ResponseScoreOptionManager responseScoreOptionManager) {
		this.responseScoreOptionManager = responseScoreOptionManager;
	}

	@Autowired
	public void setUser1Manager(
			@Qualifier("user1Manager") User1Manager user1Manager) {
		this.user1Manager = user1Manager;
	}

	// Basic Houskeeping
	// ----------------------------------------------------------------------------------------------------
	// -----------------------------------------------------------------------------------------------------
	// -----------------------------------------------------------------------------------------------------
	public String cancel() {
		if (log.isDebugEnabled())
			log.debug("Inside userTest.cancel() method");
		return "cancelTest";
	}

	private void reinitializeVariables() {
		maxTime = -1.;
		maxItems = -1;
		minError = -1.;
		itemCount = 0;
		pageCount = 0;

		allSimpleItems = new ArrayList<List<Item>>();
		allAnswItems = new ArrayList<List<AnsweredItem>>();
		currentAnswerItems = new ArrayList<AnsweredItem>();
		displayTestResults = new ArrayList<DisplayTestResult>();
		adaptivParamList = new ArrayList<AdaptiveMethods>();

		currentSectionPos = 0;
		inIndices = new ArrayList<Integer>();
		outIndices = new ArrayList<Integer>();

		if (randomSeed)
			rand = new Random();
		else
			rand = new Random(12345678);
	}

	public String compileBasicObjects() {
		HttpServletRequest request = getRequest();
		String testInstanceId = request.getParameter("testInstanceObjectId");

		if (testInstanceId == null || testInstanceId.equals("")) {
			System.out
			.println("Error with parameter passed in UserTest.compileBasicObjects()");
			return "error";
		}
		testInstance = testInstanceManager.get(new Long(testInstanceId));
		if (testInstance == null) {
			System.out
			.println("No test instance with given id found in UserTest.compileBasicObjects()");
			return "error";
		}
		testTemplate = testInstance.getTestTemplate();
		subtests = testInstanceManager.getSubTests(""
				+ testInstance.getTestTemplate().getObjectId());
		sections = testSectionManager.getTestSectionByTemplate(testTemplate
				.getObjectId() + "");
		Collections.sort(sections, new CompSectionsByOrder());
		algorithm = testInstance.getTestScoringAlgorithm().getAlgorithm();
		System.out.println("algorithm is " + algorithm);

		if (!isBySection())
			for (int i = 0; i < subtests.size(); i++)
				allAnswItems.add(new ArrayList<AnsweredItem>());

		if (isApplyStopCondition())
			parseStopCond(testTemplate);
		return "";
	}

	// Take Test
	// ----------------------------------------------------------------------------------------------------
	// -----------------------------------------------------------------------------------------------------
	// -----------------------------------------------------------------------------------------------------

	public String takeTest() {

		if (timeMeasurements)
			tic = new GregorianCalendar().getTimeInMillis();
		reinitializeVariables();
		if (timeMeasurements) {
			tac = new GregorianCalendar().getTimeInMillis();
			System.out.println("TIMING: " + (tac - tic)
					+ " ms to initialize variables");
		}

		if (timeMeasurements)
			tic = new GregorianCalendar().getTimeInMillis();
		if (compileBasicObjects().equals("error")) {
			System.out.println("Error returned by compileBasicObjs");
			return "error";
		}
		if (timeMeasurements) {
			tac = new GregorianCalendar().getTimeInMillis();
			System.out.println("TIMING: " + (tac - tic)
					+ " ms to compile basic objects");
		}

		System.out.println("Basic objects compiled");

		if (timeMeasurements)
			tic = new GregorianCalendar().getTimeInMillis();
		// Get All Items (by section, or by template)
		if (isBySection())
			for (TestSection sect : sections)
				allSimpleItems.add(itemManager.getTestMapEntryBySection(sect.getObjectId() + ""));//Sorting is done later
		else
			for (TestTemplate sub : subtests)
				allSimpleItems.add(itemManager.getTestMapEntryByTemplate(sub.getObjectId() + ""));
		if (timeMeasurements) {
			tac = new GregorianCalendar().getTimeInMillis();
			System.out.println("TIMING: " + (tac - tic)
					+ " ms to load all items in mem");
		}

		if (isIRTScored()) 
			for (TestTemplate subtest : subtests) {
				AdaptiveMethods adaptParams = new AdaptiveMethods();
				adaptParams.setTestInstance(testInstance);
				adaptParams.setTestTemplate(subtest);
				adaptivParamList.add(adaptParams);
			}
	

		if (timeMeasurements)
			tic = new GregorianCalendar().getTimeInMillis();
		if (isBySection())
			currentSectionPos = 0;
		else {
			if (isSingleItem())
				fetchNextItem();
			else
				fetchNextList();
		}
		if (timeMeasurements) {
			tac = new GregorianCalendar().getTimeInMillis();
			System.out.println("TIMING: " + (tac - tic)
					+ " ms to fetch first item");
		}

		if (timeMeasurements)
			tic = new GregorianCalendar().getTimeInMillis();
		// currentSectionPos, and in/out indices have been set. Ready to display
		// first page of the test
		prepareCurrentScreen();
		if (timeMeasurements) {
			tac = new GregorianCalendar().getTimeInMillis();
			System.out.println("TIMING: " + (tac - tic)
					+ " ms to prepare first item screen");
		}

		System.out.println("About to load first screen");
		return "takeTest";
	}

	public String prev() {
		if (isBySection()) {
			allAnswItems.remove(--currentSectionPos);
			prepareCurrentScreen();
			return null;
		}
		return null;
	}

	public String next() {
		for (AnsweredItem ans : currentAnswerItems)
			ans.updateOnGivenSelection();

		System.out.println("Inside next() method; currentAnswerItems updated with user answers");
		if (isBySection()) {//TODO if isBySimpleSums? What about random?
			allAnswItems.add(currentAnswerItems);
			currentSectionPos++;
		}

		if (isAdaptive()) {
			for (int i = 0; i < outIndices.size(); i++) {

				adaptivParamList.get(outIndices.get(i)).addItem(
						currentAnswerItems.get(i));
				if (isDichot())
					adaptivParamList.get(outIndices.get(i)).updateDichot();
				if (isPolyt()) {
					try {
						adaptivParamList.get(outIndices.get(i)).updatePolyt();
					} catch (Exception e) {
						e.printStackTrace();
					}
				}
				allAnswItems.get(outIndices.get(i)).add(
						currentAnswerItems.get(i));
				if (!isSingleItem()) {
					error = adaptivParamList.get(outIndices.get(i)).getError();
					if (isErrorStop())
						allSimpleItems.get(outIndices.get(i)).clear();
				}
			}

			if (timeMeasurements)
				tic = new GregorianCalendar().getTimeInMillis();

			if (!isStatic()) {
				if (isApplyStopCondition()) {
					currentTime = new GregorianCalendar().getTimeInMillis();
					testTime = (double) (currentTime - testStartTime) / 60000.0;
				}
				if (isSingleItem()) {
					if (isIRTScored())
						error = adaptivParamList.get(outIndices.get(0))
						.getError();
					if ((fetchNextItem() == null) || isItemStop()
							|| isTimeStop() || isErrorStop()) {
						submitTestToBackend();
						return "finishedTest";
					}
				}
				if (!isSingleItem()) {
					if (fetchNextList().equals("empty")
							|| isItemPerSubtestStop() || isTimeStop()) {
						submitTestToBackend();
						return "finishedTest";
					}
				}

			}
			if (timeMeasurements) {
				tac = new GregorianCalendar().getTimeInMillis();
				System.out.println("TIMING: " + (tac - tic)
						+ " ms to fetch next item");
			}
		}
		if (timeMeasurements)
			tic = new GregorianCalendar().getTimeInMillis();
		prepareCurrentScreen();
		if (timeMeasurements) {
			tac = new GregorianCalendar().getTimeInMillis();
			System.out.println("TIMING: " + (tac - tic)
					+ " ms to prepare next next item screen");
		}
		return null;
	}

	private void prepareCurrentScreen() {
		currentAnswerItems = new ArrayList<AnsweredItem>();
		List<Item> items = new ArrayList<Item>();

		if (isBySection()) {
			if (currentSectionPos >= allSimpleItems.size()) {
				System.out
				.println("current section index out of bounds UserTest.prepareCurrentScreen())");
				return;
			}
			items = allSimpleItems.get(currentSectionPos);
		} else {
			for (int i = 0; i < outIndices.size(); i++) {
				//Read item(s) to display and remove from pool
				items.add(removeItem((outIndices.get(i)), inIndices.get(i)));
			}
		}

		if (items == null || items.size() == 0) {
			System.out
			.println("No items in item list to be displayed (UserTest.prepareCurrentScreen())");
			return;
		}
		for (Item itm : items) {
			String optRange = itm.getResponseScoreRange().getObjectId() + "";
			List<ResponseScoreOption> options = responseScoreOptionManager
					.getResponseOptionByRangeId(optRange);// TODO move this in answeredItem
			currentAnswerItems.add(new AnsweredItem(itm, itemCount++,
					testInstance, options));
		}
		if (isBySection())
			Collections.sort(currentAnswerItems, new CompAnsItemsByItemOrderInSection());
		pageCount++;
	}

	// Save Results
	// ----------------------------------------------------------------------------------------------------
	// -----------------------------------------------------------------------------------------------------
	// -----------------------------------------------------------------------------------------------------
	public String submitTestToBackend() {
		if (log.isDebugEnabled())
			log.debug("Inside userTest.SubmitTestToBackEnd() method");

		if (isBySection()) {
			for (AnsweredItem ans : currentAnswerItems)
				ans.updateOnGivenSelection();
			allAnswItems.add(currentAnswerItems);
		}

		if (timeMeasurements && saveResults)
			tac2 = new GregorianCalendar().getTimeInMillis();
		save(); // save user answers
		calculateAndSaveScore(); // save template scores

		// Set Demo Flag to display Demographics prompt
		HttpServletRequest request = (HttpServletRequest) getFacesContext()
				.getExternalContext().getRequest();
		User user = user1Manager.getUserByUsername(request.getRemoteUser());
		List<Attribute> attributes = new ArrayList<Attribute>();
		AttributeSet set = user.getOrgs().getAttSet();
		if (set == null)
			attributes = null;
		else
			attributes = set.getAtts();
		if (attributes == null || attributes.isEmpty()
				|| (user.isDemoDone() != null && user.isDemoDone()))
			displayDemo = false;
		else
			displayDemo = true;

		// Save test instance
		TestInstanceState state = new TestInstanceState();
		state.setObjectId(new Long(1002));
		state.setState("COMPLETED");
		testInstance.setTestInstanceState(state);
		testInstance.setTestEndDate(new Date());
		if (timeMeasurements && saveResults)
			tic = new GregorianCalendar().getTimeInMillis();
		if (saveResults)
			testInstanceManager.save(testInstance);
		if (timeMeasurements && saveResults) {
			tac = new GregorianCalendar().getTimeInMillis();
			System.out.println("TIMING: " + (tac - tic)
					+ " ms to save testInstance");
		}

		if (timeMeasurements) {
			tac2 = new GregorianCalendar().getTimeInMillis();
			System.out.println("TIMING: " + (tac2 - tic2)
					+ " ms to save results");
		}

		return "finishedTest";
	}

	public void save() {
		for (List<AnsweredItem> list : allAnswItems)
			for (AnsweredItem ans : list) {
				TestItemResponseInstance responseInstance = new TestItemResponseInstance();
				responseInstance.setRelatedItem(ans.getItem());
				responseInstance.setTestInstance(ans.getTestInstance());
				responseInstance.setOrder(ans.getAnswerOrder());
				responseInstance.setRelatedResponseOption(ans
						.getSelectedOptionEntity());
				if (timeMeasurements && saveResults)
					tic = new GregorianCalendar().getTimeInMillis();
				if (saveResults)
					itemResponseManager.save(responseInstance);
				if (timeMeasurements && saveResults) {
					tac = new GregorianCalendar().getTimeInMillis();
					System.out.println("TIMING: " + (tac - tic)
							+ " ms to save user responses");
				}
			}
		String key = "testInstance.saved";
		addMessage(key);
	}

	private void calculateAndSaveScore() {

		TestInstanceScore score=null;
		List<List<AnsweredItem>> allAnswersByTemplate=null;

		if(isBySection()){
			if(isIRTScored()) updateAdaptiveList();
			else allAnswersByTemplate = allAnswersByTemplate();
		}
		
		for (int i = 0; i < subtests.size(); i++) {
			if (!isIRTScored())
				score = TestScoreUtil.simpleSumsScores(allAnswersByTemplate
						.get(i));
			if (isIRTScored())
				score = TestScoreUtil.adaptiveTestScore(adaptivParamList
						.get(i));

			if (!saveResults) {
				System.out.println("------------------------------------");
				System.out.println("max score is " + score.getMaxScore());
				System.out.println("raw score is " + score.getRawScore());
				System.out.println("percentage score is "+ score.getPercentageScore());
				System.out.println("zscore is " + score.getZetaScore());
				System.out.println("percentile is "+ score.getPercentileRank());
				System.out.println("sten score is " + score.getStenScore());
				System.out.println("theta EAP is " + score.getThetaEAP());
				System.out.println("theta ML is " + score.getThetaML());
				System.out.println("theta MAP is " + score.getThetaMAP());
				System.out.println("errorEAP is " + score.getErrorEAP());
				System.out.println("errorML is " + score.getErrorML());
				System.out.println("errorMAP is " + score.getErrorMAP());
			}

			if (timeMeasurements && saveResults)
				tic = new GregorianCalendar().getTimeInMillis();
			if (saveResults)
				testInstanceManager.save(score);
			if (timeMeasurements && saveResults) {
				tac = new GregorianCalendar().getTimeInMillis();
				System.out.println("TIMING: " + (tac - tic)
						+ " ms to save scores");
			}
		}

	}

	// Select next item
	// ----------------------------------------------------------------------------------------------------
	// -----------------------------------------------------------------------------------------------------
	// -----------------------------------------------------------------------------------------------------

	public Item fetchNextItem() {
		inIndices.clear();
		outIndices.clear();
		if (selectSublist().equals("empty")) {
			System.out.println("No more question sublists");
			return null;
		}

		if (algorithm.equals("RANDOM"))
			inIndices.add(rand.nextInt(allSimpleItems.get(outIndices.get(0))
					.size()));
		if (algorithm.equals("ADAPTIVE_DICHOTOMOUS")
				|| algorithm.equals("ADAPTIVE_POLYTOMOUS"))
			if (selectItemFromList(outIndices.get(0)).equals("empty")) {
				System.out.println("No more items in sublist");
				return null;
			}

		Item itm = allSimpleItems.get(outIndices.get(0)).get(inIndices.get(0));
		// outIndices updated in selectSublist method
		// inIndices updated in SelectItemFromListMethod (except RANDOM, see
		// above)
		return itm;
	}

	public String fetchNextList() {
		System.out.println("Inside fetchNextList");

		outIndices.clear();
		inIndices.clear();
		boolean noData = true;
		for (int i = 0; i < allSimpleItems.size(); i++) {
			List<Item> list = allSimpleItems.get(i);
			if (list == null || list.isEmpty())
				continue;
			outIndices.add(i);
			selectItemFromList(i);
			noData = false;
		}
		if (noData)
			return "empty";

		Integer seed = rand.nextInt();
		System.out.println("Before shuffle");
		for (int i = 0; i < outIndices.size(); i++)
			System.out.println("outInd is " + outIndices.get(i)
					+ ", inInd is " + inIndices.get(i));
		Collections.shuffle(outIndices, new Random(seed));
		Collections.shuffle(inIndices, new Random(seed));
		System.out.println("After shuffle");
		for (int i = 0; i < outIndices.size(); i++)
			System.out.println("outInd is " + outIndices.get(i)
					+ ", inInd is " + inIndices.get(i));

		return "notEmpty";
	}

	public String selectSublist() {
		Double max = -100000000000.0;
		Integer outIndex = -1;
		int tieCount = 0;
		Double metric = 0.;
		boolean madeChange = false;

		if (allSimpleItems == null | allSimpleItems.size() == 0)
			return "empty";
		for (int i = 0; i < allSimpleItems.size(); i++) {
			if (allSimpleItems.get(i) == null
					|| allSimpleItems.get(i).size() == 0)
				continue;
			madeChange = true;
			// DEFINE METRICS FOR DIFFERENT ALGORITHMS HERE:
			if (algorithm.equals("RANDOM"))
				metric = (double) allSimpleItems.get(i).size(); // define metric
			// to be
			// left/total ?
			if (algorithm.equals("ADAPTIVE_DICHOTOMOUS")
					|| algorithm.equals("ADAPTIVE_POLYTOMOUS"))
				metric = adaptivParamList.get(i).getError();

			if (metric > max) {
				outIndex = i;
				max = metric;
				tieCount = 1;
			} else if (java.lang.Math.abs(metric - max) < 0.0001)
				if (rand.nextDouble() < 1. / ++tieCount)
					outIndex = i;
		}
		if (!madeChange)
			return "empty"; // All subtests finished
		// outIndices.clear();
		outIndices.add(outIndex);
		return "notEmpty";
	}

	/*
	 * public String selectItemFromAll(){ double max = Double.NEGATIVE_INFINITY;
	 * int tieCount=0; return "finished"; }
	 */

	public String selectItemFromList(Integer sublistInd) {
		double max = -10000000000.0;
		int tieCount = 0;
		double metric = 0.0;
		Integer inIndex = -1;
		List<Item> list = allSimpleItems.get(sublistInd);

		if (list == null || list.isEmpty()) {
			System.out.println("List in selectItemFromList is null/empty!!!");
			return "finished";
		}

		for (int i = 0; i < list.size(); i++) {
			// DEFINE METRICS FOR DIFFERENT ALGORITHMS HERE:
			if (isIRTScored()) {
				Item itm = list.get(i);
				List<Double> b = new ArrayList<Double>();
				if (isPolyt()) {
					String optRange = itm.getResponseScoreRange().getObjectId()
							+ "";
					List<ResponseScoreOption> options = responseScoreOptionManager
							.getResponseOptionByRangeId(optRange);// TODO mv
					// this to
					// AnsweredItem
					AnsweredItem ansItm = new AnsweredItem(itm, -1,
							testInstance, options);
					b = ansItm.getThresholds();
				}
				double theta = adaptivParamList.get(sublistInd).getAbilityEst();
				if (isDichot())
					metric = MathematicalFunctions.FisherInfo3PL(theta,
							itm.getA(), itm.getB(), itm.getC());
				if (isPolyt())
					metric = MathematicalFunctions.FisherInfoGRM(theta,
							itm.getA(), b);
			}
			if (metric > max) {
				inIndex = i;
				max = metric;
				tieCount = 1;
			} else if (java.lang.Math.abs(metric - max) < 0.0001)
				if (rand.nextDouble() < 1. / ++tieCount)
					inIndex = i;
		}
		// inIndices.clear();
		inIndices.add(inIndex);
		return "notEmpty";
	}

	public Item removeItem(int outIndex, int inIndex) {
		List<Item> list = allSimpleItems.get(outIndex);
		Item itm = list.remove(inIndex);
		allSimpleItems.remove(outIndex);
		allSimpleItems.add(outIndex, list);
		return itm;
	}

	// Reports
	// ----------------------------------------------------------------------------------------------------
	// -----------------------------------------------------------------------------------------------------
	// -----------------------------------------------------------------------------------------------------

	public String inlineReport() {
		
		reinitializeVariables();
		if (compileBasicObjects().equals("error"))
			return "error";		

		if (isDisplayResult()) {

			// get all the test score for templates and the instance
			if (subtests == null || subtests.size() == 0) {
				System.out.println("No subtests avialable in inlineReport() !!!");
				return "error";
			}
			List<TestInstanceScore> scores = testInstanceManager
					.getTestInstanceScore(testInstance.getObjectId() + "");
			for (TestInstanceScore score : scores) displayTestResults.add(new DisplayTestResult(score));
			
			return "showUserResult";
		}
		return "mainMenu"; // if !isDisplayResult();
	}

	public void PDFReport() throws IOException {

		// Based on:
		// http://stackoverflow.com/questions/3592058/how-to-send-byte-as-pdf-to-browser-in-java-web-application

		HttpServletRequest request = getRequest();	
		String instanceId = request.getParameter("testInstanceObjectId");
		testInstance = testInstanceManager.getTestInstance(instanceId);

		List<DisplayTestResult> data = new ArrayList<DisplayTestResult>();
		List<TestInstanceScore> scores = testInstanceManager.getTestInstanceScore(testInstance.getObjectId()+"");
		for(TestInstanceScore score:scores) data.add(new DisplayTestResult(score));

		PDFReportManager managerPDF = new PDFReportManager(testInstance, data);
		byte[] pdfBytes = managerPDF.generatePDF("ngui");

		FacesContext facesContext = FacesContext.getCurrentInstance();
		ExternalContext externalContext = facesContext.getExternalContext();
		HttpServletResponse response = (HttpServletResponse) externalContext
				.getResponse();
		// Initialize response.
		response.reset(); // Some JSF component library or some Filter might
		// have set some headers in the buffer beforehand.
		// We want to get rid of them, else it may collide.
		response.setContentType("application/pdf"); // Check
		// http://www.iana.org/assignments/media-types
		// for all types. Use if
		// necessary
		// ServletContext#getMimeType()
		// for auto-detection based
		// on filename.
		// response.setHeader("Content-disposition",
		// "attachment; filename=\"report.pdf\""); // Comment no longer applies
		// The Save As popup magic is done here. You can give it any filename
		// you want, this only won't work in MSIE, it will use current request
		// URL as filename instead.
		response.setHeader("Content-disposition",
				"attachment; filename=\"report.pdf\"");

		// Write file to response.
		OutputStream output = response.getOutputStream();
		output.write(pdfBytes);
		output.close();

		// Inform JSF to not take the response in hands.
		facesContext.responseComplete(); // Important! Else JSF will attempt to
		// render the response which
		// obviously will fail since it's
		// already written with a file and
		// closed.

	}

	// Utilities
	// ----------------------------------------------------------------------------------------------------
	// -----------------------------------------------------------------------------------------------------
	// -----------------------------------------------------------------------------------------------------

	public void parseStopCond(TestTemplate tt) {
		String str = tt.getStopCondition();
		if (str == null || str.isEmpty())
			return;
		String delims = "[,:]";
		String[] tokens = str.split(delims);
		for (int i = 0; i < tokens.length; i++)
			tokens[i] = tokens[i].trim();
		for (int i = 0; i < tokens.length; i++)
			if (tokens[i].equals("TIME")) {
				maxTime = Double.parseDouble(tokens[i + 1]);
				if (log.isDebugEnabled())
					log.debug("time constraint set to " + maxTime + " minutes");
				break;
			}
		for (int i = 0; i < tokens.length; i++)
			if (tokens[i].equals("ITEMS")) {
				maxItems = Integer.parseInt(tokens[i + 1]);
				if (log.isDebugEnabled())
					log.debug("item count constraint set to " + maxItems
							+ " items");
				break;
			}
		for (int i = 0; i < tokens.length; i++)
			if (tokens[i].equals("ERROR")) {
				minError = Double.parseDouble(tokens[i + 1]);
				if (log.isDebugEnabled())
					log.debug("min error set to " + minError);
				break;
			}
	}

	public List<List<AnsweredItem>> allAnswersByTemplate() {
		List<List<AnsweredItem>> result = new ArrayList<List<AnsweredItem>>();
		List<Integer> templateIds = new ArrayList<Integer>();
		boolean added;
		for (List<AnsweredItem> l : allAnswItems) {
			for (AnsweredItem ans : l) {
				added = false;
				for (int i = 0; i < templateIds.size(); i++)
					if (ans.getItem().getTestTemplate().getObjectId() == templateIds
					.get(i)) {
						result.get(i).add(ans);
						added = true;
					}
				if (!added) {
					templateIds.add(ans.getItem().getTestTemplate()
							.getObjectId());
					result.add(new ArrayList<AnsweredItem>());
					result.get(result.size() - 1).add(ans);
				}
			}
		}
		return result;
	}
	
	public void updateAdaptiveList() {
		List<List<AnsweredItem>> itemsByTemplate = allAnswersByTemplate();
		
		for(int i=0; i<adaptivParamList.size(); i++){			
			for(AnsweredItem item:itemsByTemplate.get(i)){
				adaptivParamList.get(i).addItem(item);
			}
			if(isDichot()) adaptivParamList.get(i).updateDichot();
			if(isPolyt()) {
				try{
					adaptivParamList.get(i).updatePolyt();
				}catch(Exception e){
					e.printStackTrace();
				}
			}
		}
		return;
	}

}
